use std::collections::BTreeMap;

use proc_macro2::TokenStream;
use quote::{format_ident, quote};





#[derive(Debug)]
enum OutMode {
    PP,
    OD,
}

#[derive(Debug)]
enum InMode {
    UP,
    DOWN,
    FLOAT,
}

#[derive(Debug)]
struct AfMode {
    af: u8,
    out_mode: OutMode,
    in_mode: InMode,
}

#[derive(Debug)]
enum Mode {
    OUT(OutMode),
    IN(InMode),
    ANALOG,
    AF(AfMode),
}

impl InMode {
    fn parse(attr: &str) -> InMode {
        match attr {
            "UP" => InMode::UP,
            "DOWN" => InMode::DOWN,
            "FLOAT" => InMode::FLOAT,
            _ => panic!("invalid input"),
        }
    }
}

impl OutMode {
    fn parse(attr: &str) -> OutMode {
        match attr {
            "PP" => OutMode::PP,
            "OD" => OutMode::OD,
            _ => panic!("invalid out mode"),
        }
    }
}

impl Mode {
    fn parse(attr: &[&str]) -> Mode {
        match attr[0] {
            "OUT" => Mode::OUT(OutMode::parse(attr[1])),
            "IN" => Mode::IN(InMode::parse(attr[1])),
            "ANALOG" => Mode::ANALOG,
            _ => {
                let af = attr[0];
                let af_head = &af[..2];
                if af_head != "AF" {
                    panic!("invalid mode");
                }
                let af = &af[2..];
                let af: u8 = af.parse().unwrap();
                if af > 15 {
                    panic!("invalid af");
                }
                Mode::AF(AfMode {
                    af,
                    out_mode: OutMode::parse(attr[1]),
                    in_mode: InMode::parse(attr[2]),
                })
            }
        }
    }
}

#[derive(Debug)]
struct PinConfig {
    name: String,
    port: String,
    pin: u8,
    mode: Mode,
}

fn parse_port_pin(s: &str) -> (String, u8) {
    let port = &s[1..2];
    let pin = &s[2..];
    let pin: u8 = pin.parse().unwrap();
    (port.to_string(), pin)
}

impl PinConfig {

    fn parse(attr: &str) -> Self {
        let attrs: Vec<&str> = attr.trim().split("_").collect();
        let name = attrs[0].to_string();
        let (port, pin) = parse_port_pin(attrs[1]);
        let mode = Mode::parse(&attrs[2..]);
        Self { name, port, pin, mode }
    }
}

fn create_impl(port: &str, configs: &[PinConfig]) -> TokenStream {
    let mut expand = quote! {};
    let mut mode_vec = Vec::new();
    let mut ospeed_vsc = Vec::new();
    let mut pupd_vec = Vec::new();
    let mut ot_vec = Vec::new();
    let mut afl_vec = Vec::new();
    let mut afh_vec = Vec::new();
    let mut bsrr_vec = Vec::new();
    for config in configs {
        let mode = format_ident!("moder{}", config.pin);
        let speed = format_ident!("ospeedr{}", config.pin);
        let pupd = format_ident!("pupdr{}", config.pin);
        let otype = format_ident!("ot{}", config.pin);

        match &config.mode {
            Mode::OUT(out_mode) => {
                mode_vec.push(quote! {
                    .#mode().output()
                });
                ospeed_vsc.push(quote! {
                    .#speed().very_high_speed()
                });
                match out_mode {
                    OutMode::OD => {    
                        ot_vec.push(quote! {
                            .#otype().open_drain()
                        });
                        pupd_vec.push(quote! {
                            .#pupd().pull_up()
                        });
                        let bs = format_ident!("bs{}", config.pin);
                        bsrr_vec.push(quote! {
                            .#bs().set()
                        });
                    },
                    OutMode::PP => {
                        ot_vec.push(quote! {
                            .#otype().push_pull()
                        });
                        let br = format_ident!("br{}", config.pin);
                        bsrr_vec.push(quote! {
                            .#br().reset()
                        });
                    }
                };
            },
            Mode::IN(input_mode) => {
                mode_vec.push(quote! {
                    .#mode().input()
                });
                match input_mode {
                    InMode::DOWN => {
                        pupd_vec.push(quote! {
                            .#pupd().pull_down()
                        });
                    },
                    InMode::FLOAT => {
                        pupd_vec.push(quote! {
                            .#pupd().floating()
                        });
                    },
                    InMode::UP => {
                        pupd_vec.push(quote! {
                            .#pupd().pull_up()
                        });
                    }
                };
            },
            Mode::ANALOG => {
                mode_vec.push(quote! {
                    .#mode().analog()
                });
            },
            Mode::AF(AfMode { af, out_mode, in_mode }) => {
                let af = *af;
                mode_vec.push(quote! {
                    .#mode().alternate()
                });
                ospeed_vsc.push(quote! {
                    .#speed().very_high_speed()
                });
                let af_v = format_ident!("af{}", af);
                if config.pin < 8 {
                    let afl = format_ident!("afrl{}", config.pin);
                    afl_vec.push(quote! {
                        .#afl().#af_v()
                    });
                } else {
                    let afh = format_ident!("afrh{}", config.pin);
                    afh_vec.push(quote! {
                        .#afh().#af_v()
                    });
                }
                match out_mode {
                    OutMode::OD => {    
                        ot_vec.push(quote! {
                            .#otype().open_drain()
                        });
                    },
                    OutMode::PP => {
                        ot_vec.push(quote! {
                            .#otype().push_pull()
                        });
                    }
                };
                match in_mode {
                    InMode::DOWN => {
                        pupd_vec.push(quote! {
                            .#pupd().pull_down()
                        });
                    },
                    InMode::FLOAT => {
                        pupd_vec.push(quote! {
                            .#pupd().floating()
                        });
                    },
                    InMode::UP => {
                        pupd_vec.push(quote! {
                            .#pupd().pull_down()
                        });
                    }
                };
            },
        };
    }
    let port = format_ident!("P{}", port.to_uppercase());
    let mode = quote! {
        #port.moder.modify(|_, w| {
            w #(#mode_vec)*
        });
    };
    expand.extend(mode);

    if !ospeed_vsc.is_empty() {
        expand.extend(quote! {
            #port.ospeedr.modify(|_, w| {
                w #(#ospeed_vsc)*
            });
        });
    }
    if !pupd_vec.is_empty() {
        expand.extend(quote! {
            #port.pupdr.modify(|_, w| {
                w #(#pupd_vec)*
            });
        });
    }
    if !ot_vec.is_empty() {
        expand.extend(quote! {
            #port.otyper.modify(|_, w| {
                w #(#ot_vec)*
            });
        });
    }

    if !afl_vec.is_empty() {
        expand.extend(quote! {
            #port.afrl.modify(|_, w| {
                w #(#afl_vec)*
            });
        });
    }

    if !afh_vec.is_empty() {
        expand.extend(quote! {
            #port.afrh.modify(|_, w| {
                w #(#afh_vec)*
            });
        });
    }

    if !bsrr_vec.is_empty() {
        expand.extend(quote! {
            #port.bsrr.write(|w| {
                w #(#bsrr_vec)*
            });
        });
    }

    expand
}

fn create_set(config: &PinConfig) -> TokenStream {
    let name = config.name.to_lowercase();
    let set_high = format_ident!("{}_set_high", name);
    let set_low = format_ident!("{}_set_low", name);
    let toggle = format_ident!("{}_toggle", name);
    let bs = format_ident!("bs{}", config.pin);
    let br = format_ident!("br{}", config.pin);
    let odr = format_ident!("odr{}", config.pin);
    let port = format_ident!("P{}", config.port.to_uppercase());
    let expand = quote! {

        #[inline(always)]
        pub fn #set_high(&self) {
            #port.bsrr.write(|w| {
                w.#bs().set()
            });
        }

        #[inline(always)]
        pub fn #set_low(&self) {
            #port.bsrr.write(|w| {
                w.#br().reset()
            });
        }

        #[inline(always)]
        pub fn #toggle(&self) {
            #port.odr.modify(|r, w| {
                w.#odr().bit(!r.#odr().bit())
            });
        }
    };
    expand
}

pub fn create(attr: &str) -> TokenStream {
    let attrs: Vec<&str> = attr.split(",").collect();
    
    let mut expand = quote! {};
    let mut map: BTreeMap<String, Vec<PinConfig>> = BTreeMap::new();
    let mut handlers = Vec::new();
    for attr in attrs {
        let config = PinConfig::parse(attr);
        handlers.push( create_set(&config) );
        let port: &str = &config.port;
        if let Some(vec) = map.get_mut(port) {
            vec.push(config);
        } else {
            let key = port.to_string();
            let mut vec = Vec::new();
            vec.push(config);
            map.insert(key, vec);
        }
    }
    let mut rcc_vec = Vec::new();
    for k in map.keys() {
        let q = format_ident!("gpio{}en", k.to_lowercase());
        rcc_vec.push(quote! {
            .#q().enabled()
        });
    }
    expand.extend(quote! {
        RCC.ahb1enr.modify(|_, w| {
            w #(#rcc_vec)*
        });
    });
    for (k, v) in map.iter() {
        let sc = create_impl(k, v);
        expand.extend(sc);
    }
    
    quote! {
        #[inline(always)]
        pub fn init(&self) {
            #expand
        }

        #(#handlers)*
    }
}




